#include "TrackpadEmulator.h"
#include "core/utils/XComponentUtils.h"
#include "core/utils/LimelightUtils.h"
#include <Limelight.h>
#include <cassert>
#include <cstdint>

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN  0x0
#define LOG_TAG     "TrackpadEmulator"
#include <hilog/log.h>


namespace {
constexpr bool DEBUG = false;
}

inline const std::chrono::microseconds TrackpadEmulator::pollDelay = std::chrono::seconds{1} / 120;

TrackpadEmulator::TrackpadEmulator(const Config &overrides) {
    applyConfig(overrides);
}

TrackpadEmulator::~TrackpadEmulator() {
}

void TrackpadEmulator::applyConfig(const Config &overrides) {
    std::unique_lock l(mtx);
    ATSCH_EXTRACT_CONFIG(moveAcceleration);
    ATSCH_EXTRACT_CONFIG(scrollAcceleration);
    ATSCH_EXTRACT_CONFIG(tapTimeoutMs);
    ATSCH_EXTRACT_CONFIG(deadzonePx);
    ATSCH_EXTRACT_CONFIG(gestureComboTimeoutMs);
}

void TrackpadEmulator::handle(OH_NativeXComponent *const xcomp, void *const win, const TouchEvent &touchEvent) {
    Coord deltaCenter = {0, 0};
    handleTapState(xcomp, touchEvent, deltaCenter);
    handleGestureCombo();
    handleAction(deltaCenter);
}

void TrackpadEmulator::handleTapState(OH_NativeXComponent *const xcomp, const TouchEvent &ev, Coord &deltaCenter) {
    std::unique_lock l(mtx);
    /* update finger set */
    const TapState oldTapState = tapState;
    deltaCenter = {0, 0};
    for (int p = 0; p < ev.numPoints; p++) {
        const auto &touchPoint = ev.touchPoints[p];
        if constexpr (DEBUG) {
            OH_LOG_DEBUG(LOG_APP, "handleTapState of touch point id %{public}d @ (%{public}f,%{public}f) action %{public}d",
                touchPoint.id, touchPoint.x, touchPoint.y, touchPoint.type);
        }
        OH_NativeXComponent_TouchPointToolType toolType = OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN;
        XCOMP_CALL_OR_ELSE(GetTouchPointToolType(xcomp, p, &toolType),);
        switch (toolType) {
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_FINGER:
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_PEN:
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_RUBBER:
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_BRUSH:
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_PENCIL:
        case OH_NATIVEXCOMPONENT_TOOL_TYPE_AIRBRUSH: {
            break;
        }
        default: {
            continue;
        }
        }
        switch (touchPoint.type) {
        case OH_NATIVEXCOMPONENT_DOWN: {
            fingerSet.insert({touchPoint.id, FingerTap{
                .whenDown = {touchPoint.x, touchPoint.y},
                .current = {touchPoint.x, touchPoint.y},
            }});
            break;
        }
        case OH_NATIVEXCOMPONENT_UP:
        case OH_NATIVEXCOMPONENT_CANCEL:
        case OH_NATIVEXCOMPONENT_UNKNOWN: {
            fingerSet.erase(touchPoint.id);
            break;
        }
        case OH_NATIVEXCOMPONENT_MOVE: {
            if (fingerSet.contains(touchPoint.id)) {
                Coord &current = fingerSet.at(touchPoint.id).current;
                const Coord old = current;
                current = {touchPoint.x, touchPoint.y};
                deltaCenter.x += current.x - old.x;
                deltaCenter.y += current.y - old.y;
            }
            break;
        }
        }
    }
    if (fingerSet.size()) {
        deltaCenter.x /= fingerSet.size();
        deltaCenter.y /= fingerSet.size();
    }
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "printing finger set:");
        for (const auto &[id, tap] : fingerSet) {
            OH_LOG_DEBUG(LOG_APP, "-- id %{public}d whenDown @ (%{public}f,%{public}f) current @ (%{public}f,%{public}f)",
                id, tap.whenDown.x, tap.whenDown.y, tap.current.x, tap.current.y);
        }
    }
    /* perform tap state transition action */
    const auto emitGesture = [&] (AtomicGestureType g) {
        generatedGestures.push_back({g, static_cast<int8_t>(maxTapFingers)});
    };
    switch (oldTapState) {
    case TapState::None: {
        if (!fingerSet.empty()) {
            /* first touch down, none ---> tapping */
            if constexpr (DEBUG) {
                OH_LOG_DEBUG(LOG_APP, "tap state None ---> Tapping");
            }
            tapState = TapState::Tapping;
            maxTapFingers = fingerSet.size();
            tapTimer = std::make_unique<CancellableTimer>("tap", tapTimeoutMs, [this, emitGesture] {
                std::unique_lock l(mtx);
                if (tapState != TapState::Tapping) {
                    return;
                }
                /* timeout when still tapping, tapping --> holding */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "tap state Tapping ---> Holding");
                }
                tapState = TapState::Holding;
                emitGesture(AtomicGestureType::HoldStart);
            });
        }
        break;
    }
    case TapState::Tapping: {
        if (fingerSet.empty()) {
            /* all fingers left, tapping ---> none */
            if constexpr (DEBUG) {
                OH_LOG_DEBUG(LOG_APP, "tap state Tapping ---> None");
            }
            tapState = TapState::None;
            {
                l.unlock();
                tapTimer = nullptr;
                l.lock();
            }
            emitGesture(AtomicGestureType::Tap);
            maxTapFingers = 0;
        } else {
            maxTapFingers = std::max({maxTapFingers, static_cast<decltype(maxTapFingers)>(fingerSet.size())});
            const auto centerWhenDown = [&] () -> Coord {
                assert(!fingerSet.empty());
                Coord c = {0, 0};
                for (const auto &[_, f] : fingerSet) {
                    c.x += f.whenDown.x;
                    c.y += f.whenDown.y;
                }
                c.x /= fingerSet.size();
                c.y /= fingerSet.size();
                return c;
            };
            const auto currentCenter = [&] () -> Coord {
                assert(!fingerSet.empty());
                Coord c = {0, 0};
                for (const auto &[_, f] : fingerSet) {
                    c.x += f.current.x;
                    c.y += f.current.y;
                }
                c.x /= fingerSet.size();
                c.y /= fingerSet.size();
                return c;
            };
            const auto coordDist = [] (const Coord &a, const Coord &b) {
                return std::abs(a.x - b.x) + std::abs(a.y - b.y);
            };
            if (coordDist(centerWhenDown(), currentCenter()) > deadzonePx) {
                /* center moved out of deadzone, tapping ---> holding */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "tap state Tapping ---> Holding");
                }
                tapState = TapState::Holding;
                {
                    l.unlock();
                    tapTimer = nullptr;
                    l.lock();
                }
                emitGesture(AtomicGestureType::HoldStart);
            } else {
                /* still in deadzone and not timeout, tapping ---> tapping */
                /* finger set and their center are updated automatically */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "tap state Tapping stay");
                }
            }
        }
        break;
    }
    case TapState::Holding: {
        if (fingerSet.empty()) {
            /* all fingers left, holding ---> none */
            if constexpr (DEBUG) {
                OH_LOG_DEBUG(LOG_APP, "tap state Holding ---> None");
            }
            tapState = TapState::None;
            emitGesture(AtomicGestureType::HoldEnd);
            maxTapFingers = 0;
        }
        break;
    }
    }
}


TrackpadEmulator::IAction::IAction(const ActionType &type) : type(type) {
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "action %{public}d start", static_cast<int>(type));
    }
}

TrackpadEmulator::IAction::~IAction() {
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "action %{public}d end", static_cast<int>(type));
    }
}

struct TrackpadEmulator::BaseClickAction : IAction {
    constexpr int getButtonCode() const {
        switch (type) {
        case ActionType::LeftClick: return BUTTON_LEFT;
        case ActionType::RightClick: return BUTTON_RIGHT;
        case ActionType::MiddleClick: return BUTTON_MIDDLE;
        default: throw std::runtime_error("not a click action " + std::to_string(static_cast<int>(type)));
        }
    }
    BaseClickAction(ActionType type) : IAction(type) {
        LI_CALL_OR_ELSE(SendMouseButtonEvent(BUTTON_ACTION_PRESS, getButtonCode()),);
    }
    ~BaseClickAction() override {
        LI_CALL_OR_ELSE(SendMouseButtonEvent(BUTTON_ACTION_RELEASE, getButtonCode()),);
    }
    void handle(const Coord &deltaCenter) override {}
};

struct TrackpadEmulator::LeftClickAction : BaseClickAction {
    LeftClickAction() : BaseClickAction(ActionType::LeftClick) {}
};

struct TrackpadEmulator::RightClickAction : BaseClickAction {
    RightClickAction() : BaseClickAction(ActionType::RightClick) {}
};

struct TrackpadEmulator::MiddleClickAction : BaseClickAction {
    MiddleClickAction() : BaseClickAction(ActionType::MiddleClick) {}
};

struct TrackpadEmulator::MoveAction : IAction {
    const double acc;
    MoveAction(double acc) : IAction(ActionType::Move), acc(acc) {}
    ~MoveAction() override {}
    void handle(const Coord &deltaCenter) override {
        LI_CALL_OR_ELSE(SendMouseMoveEvent(deltaCenter.x * acc, deltaCenter.y * acc),);
    }
};

struct TrackpadEmulator::DragAction : IAction {
    const double acc;
    DragAction(double acc) : IAction(ActionType::Drag), acc(acc) {
        LI_CALL_OR_ELSE(SendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT),);
    }
    ~DragAction() override {
        LI_CALL_OR_ELSE(SendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT),);
    }
    void handle(const Coord &deltaCenter) override {
        LI_CALL_OR_ELSE(SendMouseMoveEvent(deltaCenter.x * acc, deltaCenter.y * acc),);
    }
};

struct TrackpadEmulator::ScrollAction : IAction {
    const double acc;
    ScrollAction(double acc) : IAction(ActionType::Scroll), acc(acc) {}
    ~ScrollAction() override {}
    void handle(const Coord &deltaCenter) override {
        LI_CALL_OR_ELSE(SendHighResScrollEvent(deltaCenter.y * acc),);
        LI_CALL_OR_ELSE(SendHighResHScrollEvent(-1. * deltaCenter.x * acc),);
    }
};

void TrackpadEmulator::handleGestureCombo() {
    std::unique_lock l(mtx);
    for (int gIter = 0; gIter < generatedGestures.size(); gIter++) {
        const auto &g = generatedGestures.at(gIter);
        if constexpr (DEBUG) {
            OH_LOG_DEBUG(LOG_APP, "handling gesture of type %{public}d fingers %{public}d", static_cast<int>(g.gesture), g.fingers);
        }
        switch (gestureComboState) {
        case GestureComboState::None: {
            switch (g.gesture) {
            case AtomicGestureType::Tap: {
                if (g.fingers == 1) {
                    /* first single-finger tap, wait for possible gesture combo */
                    if constexpr (DEBUG) {
                        OH_LOG_DEBUG(LOG_APP, "gesture combo state None ---> SingleFingerTapped");
                    }
                    gestureComboState = GestureComboState::SingleFingerTapped;
                    gestureTimer = std::make_unique<CancellableTimer>("gesture", gestureComboTimeoutMs, [this] {
                        std::unique_lock l(mtx);
                        if (gestureComboState != GestureComboState::SingleFingerTapped) {
                            return;
                        }
                        /* single-finger tap timeout, emit left-click action */
                        if constexpr (DEBUG) {
                            OH_LOG_DEBUG(LOG_APP, "gesture combo state SingleFingerTapped ---> None, emitting left-click");
                        }
                        gestureComboState = GestureComboState::None;
                        currentAction = std::make_unique<LeftClickAction>();
                        currentAction = nullptr;
                    });
                } else {
                    /* multi-finger tap, emit click action immediately */
                    if constexpr (DEBUG) {
                        OH_LOG_DEBUG(LOG_APP, "gesture combo state None ---> None, emitting click");
                    }
                    gestureComboState = GestureComboState::None;
                    switch (g.fingers) {
                    case 2: {
                        currentAction = std::make_unique<RightClickAction>();
                        break;
                    }
                    case 3: {
                        currentAction = std::make_unique<MiddleClickAction>();
                        break;
                    }
                    default: {
                        OH_LOG_WARN(LOG_APP, "no immediate click action for %{public}d fingers", g.fingers);
                        break;
                    }
                    }
                    currentAction = nullptr;
                }
                break;
            }
            case AtomicGestureType::HoldStart: {
                /* no preceding combo, just plain panning, enter corresponding holding action */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "gesture combo state None ---> Holding");
                }
                gestureComboState = GestureComboState::NonComboHolding;
                switch (g.fingers) {
                case 1: {
                    currentAction = std::make_unique<MoveAction>(moveAcceleration);
                    break;
                }
                case 2: {
                    currentAction = std::make_unique<ScrollAction>(scrollAcceleration);
                    break;
                }
                default: {
                    OH_LOG_WARN(LOG_APP, "no hold action for %{public}d fingers", g.fingers);
                    break;
                }
                }
                break;
            }
            default: {
                OH_LOG_WARN(LOG_APP, "gesture combo state None --- %{public}d --> ! , resetting", static_cast<int>(g.gesture));
                gestureComboState = GestureComboState::None;
                currentAction = nullptr;
                break;
            }
            }
            break;
        }   /* case GestureComboState::None */
        case GestureComboState::SingleFingerTapped: {
            if (g.gesture == AtomicGestureType::HoldStart && g.fingers == 1) {
                /* single-finger hold start within single-finger tap timeout, enter drag action */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "gesture combo state SingleFingerTapped ---> SingleFingerTappedThenHolding");
                }
                gestureComboState = GestureComboState::SingleFingerTappedThenHolding;
                {
                    l.unlock();
                    gestureTimer = nullptr;
                    l.lock();
                }
                currentAction = std::make_unique<DragAction>(moveAcceleration);
            } else {
                /* otherwise emit the single-finger tap immediately, and re-handle this gesture as a new combo */
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "gesture combo state SingleFingerTapped ---> None, emitting pending left-click action");
                }
                gestureComboState = GestureComboState::None;
                {
                    l.unlock();
                    gestureTimer = nullptr;
                    l.lock();
                }
                currentAction = std::make_unique<LeftClickAction>();
                currentAction = nullptr;
                gIter--;
            }
            break;
        }   /* case GestureComboState::SingleFingerTapped */
        case GestureComboState::SingleFingerTappedThenHolding: {
            switch (g.gesture) {
            case AtomicGestureType::HoldEnd: {
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "gesture combo state SingleFingerTappedThenHolding ---> None");
                }
                gestureComboState = GestureComboState::None;
                currentAction = nullptr;
                break;
            }
            default: {
                OH_LOG_WARN(LOG_APP, "gesture combo state SingleFingerTappedThenHolding --- %{public}d --> ! , resetting", static_cast<int>(g.gesture));
                gestureComboState = GestureComboState::None;
                currentAction = nullptr;
                break;
            }
            }
            break;
        }   /* case GestureComboState::SingleFingerTappedThenHolding */
        case GestureComboState::NonComboHolding: {
            switch (g.gesture) {
            case AtomicGestureType::HoldEnd: {
                if constexpr (DEBUG) {
                    OH_LOG_DEBUG(LOG_APP, "gesture combo state NonComboHolding ---> None");
                }
                gestureComboState = GestureComboState::None;
                currentAction = nullptr;
                break;
            }
            default: {
                OH_LOG_WARN(LOG_APP, "gesture combo state NonComboHolding --- %{public}d --> ! , resetting", static_cast<int>(g.gesture));
                gestureComboState = GestureComboState::None;
                currentAction = nullptr;
                break;
            }
            }
            break;
        }   /* case GestureComboState::NonComboHolding */
        default: {
            throw std::runtime_error("unreachable: illegal gesture combo state " + std::to_string(static_cast<int>(gestureComboState)));
        }
        }
    }
    generatedGestures.clear();
}

void TrackpadEmulator::handleAction(const Coord &deltaCenter) {
    if (!currentAction) {
        return;
    }
    currentAction->handle(deltaCenter);
}
